/*
 *  Copyright 2004 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.webapp.util.core;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;

/**
 * <p>This class provides facilities to scale and convert images</p>
 * <p><a href="ImageUtil.java.html"><i>View Source</i></a></p>
 *
 * @author Andrey Grebnev <a href="mailto:andrey.grebnev@blandware.com">&lt;andrey.grebnev@blandware.com&gt;</a>
 * @version $Revision: 1.3 $ $Date: 2005/08/04 17:25:24 $
 */
public class ImageUtil {

	public static final String MIMETYPE_JPEG = "image/jpeg";
	public static final String MIMETYPE_GIF = "image/gif";
	public static final String MIMETYPE_PNG = "image/png";

	/**
	 * Creates new instance of ImageUtil
	 */
	public ImageUtil() {
	}

	/**
	 * Decodes image of specified format where image data is given as an array
     * of bytes
	 *
	 * @param imageData original image data
	 * @param mimeType  mime type of source file e.g. <code>image/jpeg</code>
	 * @return <code>BufferedImage<code> and metadata bagged into <code>IIOImage</code>
	 * @throws IllegalArgumentException if this mime type is not supported
	 * @throws IOException              if cannot perform decode operation
	 */
	public static IIOImage decodeImage(byte[] imageData, String mimeType)
	        throws IllegalArgumentException, IOException {
		return decodeImage(new ByteArrayInputStream(imageData), mimeType);
	}

	/**
	 * Decodes image of specified format where image data is given as an input
     * stream
	 *
	 * @param imageData original image data
	 * @param mimeType  mime type of source file e.g. <code>image/jpeg</code>
	 * @return <code>BufferedImage<code> and metadata bagged into <code>IIOImage</code>
	 * @throws IllegalArgumentException if this mime type is not supported
	 * @throws IOException              if cannot perform decode operation
	 */
	public static IIOImage decodeImage(InputStream imageData, String mimeType)
	        throws IllegalArgumentException, IOException {
		Iterator imageReaders = ImageIO.getImageReadersByMIMEType(mimeType);
		if ( imageReaders == null || !imageReaders.hasNext() ) {
			throw new IllegalArgumentException("Unsupported mimetype: " + mimeType);
		}
		ImageReader reader = (ImageReader) imageReaders.next();
		ImageReadParam param = reader.getDefaultReadParam();
		ImageInputStream iis = ImageIO.createImageInputStream(imageData);
		reader.setInput(iis, true, true);
		int index = reader.getMinIndex();
		BufferedImage image = reader.read(index, param);
		IIOMetadata metadata = reader.getImageMetadata(index);
		IIOImage iioImage = new IIOImage(image, null, metadata);
		iis.close();
		reader.dispose();
		return iioImage;
	}

	/**
	 * Encodes image into specified format returning the resulting image data
     * as an array of bytes
	 *
	 * @param image    image to write, can has metadata to get source compression quality
	 * @param mimeType mime type of source file e.g. <code>image/jpeg</code>
	 * @return encoded image data
	 * @throws IllegalArgumentException if this mime type is not supported
	 * @throws IOException              if cannot perform encode operation
	 */
	public static byte[] encodeImage(IIOImage image, String mimeType)
	        throws IllegalArgumentException, IOException {
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		encodeImage(image, out, mimeType);
		byte[] result = out.toByteArray();
		out.close();
		return result;
	}

	/**
	 * Encodes image into specified format putting the resulting image data to
     * a given stream
	 *
	 * @param image     image to write, can has metadata to get source compression quality
	 * @param imageData storage to put encoded data
	 * @param mimeType  mime type of source file e.g. <code>image/jpeg</code>
	 * @throws IllegalArgumentException if this mime type is not supported
	 * @throws IOException              if cannot perform encode operation
	 */
	public static void encodeImage(IIOImage image, OutputStream imageData, String mimeType)
	        throws IllegalArgumentException, IOException {
		Iterator imageWriters = ImageIO.getImageWritersByMIMEType(mimeType);
		if ( imageWriters == null || !imageWriters.hasNext() ) {
			throw new IllegalArgumentException("Unsupported mimetype: " + mimeType);
		}
		ImageWriter writer = (ImageWriter) imageWriters.next();
		ImageWriteParam param = writer.getDefaultWriteParam();
		ImageOutputStream ios = ImageIO.createImageOutputStream(imageData);
		if ( mimeType.equalsIgnoreCase(MIMETYPE_JPEG) && image.getMetadata() == null ) {
			param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
			param.setCompressionQuality(1.0f);
		}
		writer.setOutput(ios);
		writer.write(image.getMetadata(), image, param);
		ios.flush();
		writer.dispose();
	}

	/**
	 * Scales image so it can be inscribed into rectangle with specified width
     * and height. If the resulting image has the same size as a source one,
     * then a source image is returned.
	 *
	 * @param iioSource original image
	 * @param maxWidth  width of rectangle
	 * @param maxHeight height of rectangle
	 * @return scaled image
	 */
	public static IIOImage scaleToRectangle(IIOImage iioSource, int maxWidth, int maxHeight) {
		if ( !(iioSource.getRenderedImage() instanceof BufferedImage) ) {
			throw new IllegalArgumentException("RenderedImage in IIOImage must be BufferedImage");
		}

		BufferedImage source = (BufferedImage) iioSource.getRenderedImage();

		double scale = 1.0d;
		if ( maxWidth == 0 && maxHeight == 0 ) {
			scale = 1.0d;
		} else if ( maxWidth == 0 ) {
			scale = (double) maxHeight / (double) source.getHeight();
		} else if ( maxHeight == 0 ) {
			scale = (double) maxWidth / (double) source.getWidth();
		} else {
			double scaledWidth = (double) maxWidth / (double) source.getWidth();
			double scaledHeight = (double) maxHeight / (double) source.getHeight();
			scale = Math.min(scaledHeight, scaledWidth);
		}
		//do not enlarge image
		if ( scale >= 1.0d ) {
			return iioSource;
		}

		AffineTransform at = new AffineTransform();
		at.scale(scale, scale);
		AffineTransformOp atOp = new AffineTransformOp(at, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
		BufferedImage destination = atOp.filter(source, null);

		IIOImage iioDestination = new IIOImage(destination, null, null);
		return iioDestination;
	}

}
